package gitfs

import (
	"context"
	"fmt"
	"path"
	"sync"
	"syscall"
	"time"

	"gitee.com/thubcc/p2pgit/gitfs/storage"
	"gitee.com/thubcc/p2pgit/types"
	"github.com/hanwen/go-fuse/v2/fs"
	"github.com/hanwen/go-fuse/v2/fuse"
	"gopkg.in/src-d/go-git.v4/plumbing/filemode"
)

type oRoot struct {
	fs.Inode

	root  *storage.Tree
	geter types.SomeGeter
}

var _ = (fs.NodeOnAdder)((*oRoot)(nil))

func (r *oRoot) OnAdd(ctx context.Context) {

	var one func(in *fs.Inode, dir string, t *storage.Tree) error
	one = func(in *fs.Inode, dir string, t *storage.Tree) error {
		for _, e := range t.Entries {
			if e.Mode == filemode.Dir {
				ch := in.GetChild(e.Name)
				if ch == nil {
					sin := fs.Inode{}
					ch = in.NewPersistentInode(ctx, &sin,
						fs.StableAttr{Mode: fuse.S_IFDIR})
					in.AddChild(e.Name, ch, true)
				}
				sub, err := t.Tree(e.Name)
				if err != nil {
					fmt.Println(err)
					return err
				}
				err = one(ch, path.Join(dir, e.Name), sub)
				if err != nil {
					fmt.Println(err)
					return err
				}
			} else {
				file := t.TreeEntryFile(&e)
				ch := in.NewPersistentInode(ctx, &hFile{file: *file}, fs.StableAttr{})
				in.AddChild(e.Name, ch, true)
			}
		}
		return nil
	}
	one(&r.Inode, "", r.root)
}

// NewHashTree gen dir tree
func NewHashTree(master types.Hash, gs types.SomeGeter) (fs.InodeEmbedder, error) {
	s := storage.NewStorage(gs)
	commit, err := storage.GetCommit(s, master)
	if err != nil {
		fmt.Println("commit:", err)
		return nil, err
	}
	tree, err := storage.GetTree(s, commit.TreeHash)
	if err != nil {
		fmt.Println("root:", err)
		return nil, err
	}

	return &oRoot{root: tree, geter: gs}, nil
}

type hFile struct {
	fs.Inode
	file storage.File
	mu   sync.Mutex
}

var _ = (fs.NodeOpener)((*hFile)(nil))
var _ = (fs.NodeGetattrer)((*hFile)(nil))

// Getattr sets the minimum, which is the size. A more full-featured
// FS would also set timestamps and permissions.
func (gf *hFile) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) syscall.Errno {

	out.Mode = uint32(gf.file.Mode) & 07777
	out.Nlink = 1
	out.Mtime = uint64(time.Now().Unix())
	out.Atime = out.Mtime
	out.Ctime = out.Mtime
	out.Size = uint64(gf.file.Size())
	const bs = 512
	out.Blksize = bs
	out.Blocks = (out.Size + bs - 1) / bs
	return 0
}

// Open lazily unpacks zip data
func (gf *hFile) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint32, syscall.Errno) {
	gf.mu.Lock()
	defer gf.mu.Unlock()
	_, err := gf.file.Blob()
	fmt.Println("get file", err)
	// We don't return a filehandle since we don't really need
	// one.  The file content is immutable, so hint the kernel to
	// cache the data.
	return nil, fuse.FOPEN_KEEP_CACHE, 0
}

// Read simply returns the data that was already unpacked in the Open call
func (gf *hFile) Read(ctx context.Context, f fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
	data := gf.file.Bytes()
	end := int(off) + len(dest)
	if end > len(data) {
		end = len(data)
	}
	return fuse.ReadResultData(data[off:end]), 0
}
